# =================  ESP32-S3  MJPEG 接收 + 显示  =================
import socket
import time
import gc
import network
import _thread

# ------------- 1. 网络配置（静态 IP） -------------
SSID = 'LAPTOP-D3RPTUAA 6358'      # 与 ESP32-CAM 连接同一热点
PWD  = '19234949992.ch'
STATIC_IP      = '192.168.137.100'
SUBNET_MASK    = '255.255.255.0'
GATEWAY        = '192.168.137.1'   # Windows 移动热点默认网关
DNS            = '8.8.8.8'

def wifi_connect():
    sta = network.WLAN(network.STA_IF)
    sta.active(True)
    sta.ifconfig((STATIC_IP, SUBNET_MASK, GATEWAY, DNS))
    sta.connect(SSID, PWD)
    for _ in range(20):
        if sta.isconnected():
            print('WiFi connected:', sta.ifconfig())
            return sta.ifconfig()[0]
        time.sleep(1)
    raise RuntimeError('WiFi connect timeout')

ip = wifi_connect()  # ip == 192.168.137.100

# ------------- 2. 全局共享变量 -------------
latest_frame = b''
frame_lock   = _thread.allocate_lock()

# ------------- 3. TCP 接收线程 -------------
TCP_PORT = 8080   # ESP32-CAM 推流端口

def tcp_rx():
    global latest_frame
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('0.0.0.0', TCP_PORT))
    sock.listen(1)
    print('TCP receiver listening on port', TCP_PORT)

    while True:
        cl, addr = sock.accept()
        print('Camera connected from', addr)
        buf = b''
        while True:
            data = cl.recv(2048)
            if not data:
                break
            buf += data
            # 解析完整 JPEG 帧
            while True:
                start = buf.find(b'--frame\r\nContent-Type: image/jpeg\r\nContent-Length: ')
                if start == -1:
                    break
                hdr_end = buf.find(b'\r\n\r\n', start)
                if hdr_end == -1:
                    break
                try:
                    length = int(buf[start + len(b'--frame\r\nContent-Type: image/jpeg\r\nContent-Length: '):hdr_end])
                except ValueError:
                    buf = buf[hdr_end + 4:]
                    continue
                jpeg_start = hdr_end + 4
                jpeg_end   = jpeg_start + length
                if len(buf) < jpeg_end + 2:
                    break
                if buf[jpeg_end:jpeg_end + 2] != b'\r\n':
                    buf = buf[jpeg_end:]
                    continue
                # 拿到完整帧
                with frame_lock:
                    latest_frame = buf[jpeg_start:jpeg_end]
                buf = buf[jpeg_end + 2:]
        cl.close()
        print('Camera disconnected')

_thread.start_new_thread(tcp_rx, ())

# ------------- 4. HTTP 服务 -------------
HTTP_PORT = 80

html_index = b'''<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ESP32-S3 Live MJPEG</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>body{font-family:Arial;text-align:center;background:#000}img{max-width:100%;height:auto}</style>
</head>
<body>
  <h2 style="color:#fff">ESP32-S3 MJPEG Viewer</h2>
  <img src="/live" />
</body>
</html>
'''

hdr_200 = b'HTTP/1.1 200 OK\r\n'
hdr_stream = b'''\
Content-Type: multipart/x-mixed-replace; boundary=frame
Connection: close
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT\r\n\r\n'''

def http_handler(sock):
    try:
        req = sock.recv(1024)
        if b'GET / ' in req or b'GET /index.html' in req:
            sock.send(b'%sContent-Type: text/html\r\nContent-Length: %d\r\n\r\n%s'
                      % (hdr_200, len(html_index), html_index))
        elif b'GET /live' in req:
            sock.send(hdr_200)
            sock.send(hdr_stream)
            while True:
                with frame_lock:
                    frame = latest_frame
                if frame:
                    try:
                        sock.send(b'--frame\r\nContent-Type: image/jpeg\r\nContent-Length: %d\r\n\r\n%s\r\n'
                                  % (len(frame), frame))
                    except OSError:
                        break
                time.sleep(0.033)  # ~30 FPS
        else:
            sock.send(b'HTTP/1.1 404 Not Found\r\n\r\n')
    except Exception as e:
        print('HTTP handler error:', e)
    finally:
        sock.close()

def http_server():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(('0.0.0.0', HTTP_PORT))
    s.listen(3)
    print('HTTP server on http://%s:%d' % (ip, HTTP_PORT))
    while True:
        try:
            conn, addr = s.accept()
            http_handler(conn)
        except OSError:
            pass

def main():
    http_server()
    
if __name__ == "__main__":
    main()
